
// ***********************
// * GManage - thomasfn *
// ***********************
// Plugs Module (SHARED)

require( "glon" )

if ( !file.Exists( "plugs/mount.txt" ) ) then
	local b, res = pcall( glon.encode, {} )
	if ( b ) then file.Write( "plugs/mount.txt", res ) end
end

if ( SERVER ) then resource.AddFile( "data/plugs/mount.txt" ) end

module( "plugs", package.seeall )

local instances = {}
local bases = {}
local metas = {}
local hooked = {}
local dontmount = {}

local Errors = {}

local debugmode = true


// ---------------------------------------------------------------------------------------------------------------
// ReadMountData - Reads out mount data
// ---------------------------------------------------------------------------------------------------------------
function ReadMountData()
	if ( SERVER ) then
		local b, res = pcall( glon.decode, file.Read( "plugs/mount.txt" ) )
		if ( b ) then dontmount = res end
		for name, val in pairs( dontmount or {} ) do 
			local base = bases[ name ]
			if ( val && base && base.Platform != PLUG_PLATFORM_SERVER ) then SetGlobalBool( "gmpdm_" .. name, true ) end
		end
	end
	if ( CLIENT ) then
		local dontmount = {}
		for name, _ in pairs( bases ) do
			if ( GetGlobalBool( "gmpdm_" .. name ) ) then dontmount[ name ] = true end
		end
	end
end

// ---------------------------------------------------------------------------------------------------------------
// Msg - Prints a message to console
// ---------------------------------------------------------------------------------------------------------------
local function msg( message )
	if ( debugmode ) then Msg( "Plugs: ", message, "\n" ) end
end

// ---------------------------------------------------------------------------------------------------------------
// Instance - Instances a plug
// ---------------------------------------------------------------------------------------------------------------
function Instance( name )
	if ( !bases[ name ] ) then return end
	if ( !metas[ name ] ) then return end
	if ( instances[ name ] ) then return end
	
	if ( dontmount[ name ] ) then
		msg( "Plug left unmounted! (" .. name .. ")" )
		return
	end
	
	if ( CLIENT && GetGlobalBool( "gmpdm_" .. name ) ) then
		msg( "Plug left unmounted! (" .. name .. ")" )
		return
	end
	
	local o = {}
	setmetatable( o, metas[ name ] )
	o._BASE = bases[ name ]
	o._NAME = name
	if ( o.OnCreate ) then o:OnCreate() end
	
	instances[ name ] = o
end

// ---------------------------------------------------------------------------------------------------------------
// Uninstance - Removes an instance of a plug
// ---------------------------------------------------------------------------------------------------------------
function Uninstance( name )
	if ( !bases[ name ] ) then return end
	if ( !metas[ name ] ) then return end
	if ( !instances[ name ] ) then return end
	
	if ( instances[ name ].OnDestroy ) then instances[ name ]:OnDestroy() end
	
	instances[ name ] = nil
end

// ---------------------------------------------------------------------------------------------------------------
// IsInstanced - Determines if a plug is instanced or not
// ---------------------------------------------------------------------------------------------------------------
function IsInstanced( name )
	return instances[ name ] != nil
end

// ---------------------------------------------------------------------------------------------------------------
// GetAll - Gets all plugs
// ---------------------------------------------------------------------------------------------------------------
function GetAll( )
	return bases
end

// ---------------------------------------------------------------------------------------------------------------
// Register - Registers a plug
// ---------------------------------------------------------------------------------------------------------------
function Register( name, tbl )
	bases[ name ] = tbl
	metas[ name ] = { __index = tbl }
	msg( "Registered plug '" .. name .. "'!" )
end

// ---------------------------------------------------------------------------------------------------------------
// ReportError - Reports an error with the plug system
// ---------------------------------------------------------------------------------------------------------------
function ReportError( plugname, funcname, err, params )
	print( "========= * ERROR DUMP * =========" )
	Msg( "GMANAGE: Error whilst calling plug!\n" )
	Msg( "Plug Name: " .. (plugname or "Unknown") .. "\n" )
	Msg( "Function Name: " .. (funcname or "Unknown") .. "\n" )
	Msg( "Function Parameters: " )
	print( unpack( params or {} ) )
	Msg( "Message: " .. (err or "No error returned!") .. "\n" )
	print( "========= * END OF DUMP * =========" )
	if (!plugname) then return end
	Errors[ plugname ] = (Errors[ plugname ] or 0) + 1
	if (Errors[ plugname ] >= 3) then
		Msg( plugname, " has reported 3 or more errors and is unmounting itself!\n" )
		if (instances[ plugname ].OnDestroy) then
			pcall( instances[ plugname ].OnDestroy, instances[ plugname ] )
		end
		instances[ plugname ] = nil
	end
end

// ---------------------------------------------------------------------------------------------------------------
// Call - Calls a function on all plugs
// ---------------------------------------------------------------------------------------------------------------
function Call( id, ... )
	for name, plug in pairs( instances ) do
		if ( plug[ id ] ) then
			local b, result = pcall( plug[ id ], plug, ... )
			if ( !b ) then
				ReportError( name, id, result, { ... } )
			end
			if ( result != nil ) then
				return result
			end
		end
	end
end

// ---------------------------------------------------------------------------------------------------------------
// CallHalt - Calls a function on the first plug found that has it
// ---------------------------------------------------------------------------------------------------------------
function CallHalt( id, ... )
	for _, plug in pairs( instances ) do
		if ( plug[ id ] ) then
			local b, result = pcall( plug[ id ], plug, ... )
			if ( !b ) then
				ReportError( name, id, result, { ... } )
			else
				return result
			end
		end
	end
end

// ---------------------------------------------------------------------------------------------------------------
// MergeCall - Calls a function on all plugs and returns a merged response
// ---------------------------------------------------------------------------------------------------------------
function MergeCall( id, ... )
	local r = {}
	for _, plug in pairs( instances ) do
		if ( plug[ id ] ) then
			local b, result = pcall( plug[ id ], plug, ... )
			if ( !b ) then
				ReportError( name, id, result, { ... } )
			end
			if ( result != nil ) then
				if ( type( result ) == "table" ) then
					table.Add( r, result )
				end
			end
		end
	end
	return r
end

// ---------------------------------------------------------------------------------------------------------------
// MergeCallOverride - Calls a function on all plugs and returns an overrided merged response
// ---------------------------------------------------------------------------------------------------------------
function MergeCallOverride( id, ... )
	local r = {}
	for _, plug in pairs( instances ) do
		if ( plug[ id ] ) then
			local b, result = pcall( plug[ id ], plug, ... )
			if ( !b ) then
				ReportError( name, id, result, { ... } )
			end
			if ( result != nil ) then
				if ( type( result ) == "table" ) then
					table.Merge( r, result )
				end
			end
		end
	end
	return r
end

// ---------------------------------------------------------------------------------------------------------------
// Hook - Hooks a gamemode hook to a plug event
// ---------------------------------------------------------------------------------------------------------------
function Hook( p, hookname )
	if ( !IsPlatform( p ) ) then return end
	if ( hooked[ hookname ] ) then return end
	hook.Add( hookname, "PLUG:" .. hookname, function( ... ) return Call( hookname, ... ) end )
	hooked[ hookname ] = true
	msg( "Hooked event '" .. hookname .. "'!" )
end

// ---------------------------------------------------------------------------------------------------------------
// SaveMounts - Saves the don't mount table
// ---------------------------------------------------------------------------------------------------------------
local function SaveMounts()
	local b, res = pcall( glon.encode, dontmount )
	if ( b ) then file.Write( "plugs/mount.txt", res ) end
	msg( "Mounts saved!" )
end

// ---------------------------------------------------------------------------------------------------------------
// Mount - Mounts a plug
// ---------------------------------------------------------------------------------------------------------------
function Mount( name )
	dontmount[ name ] = nil
	if ( SERVER ) then
		SaveMounts()
		umsg.Start( "gmanage_mount" )
		umsg.String( name )
		umsg.End()
		SetGlobalBool( "gmpdm_" .. name, false )
	end
	Instance( name )
end

// ---------------------------------------------------------------------------------------------------------------
// Unmount - Unmounts a plug (requires restart)
// ---------------------------------------------------------------------------------------------------------------
function Unmount( name )
	dontmount[ name ] = true
	if ( SERVER ) then
		SaveMounts()
		umsg.Start( "gmanage_unmount" )
		umsg.String( name )
		umsg.End()
		SetGlobalBool( "gmpdm_" .. name, true )
	end
	Uninstance( name )
end